//
// Created by hunan on 25-7-6.
//

#ifndef LOG_UTIL_H
#define LOG_UTIL_H
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <map>
#include <mutex>
#include <string>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>

class LogDescription
{
public:
    LogDescription() = default;
    virtual ~LogDescription() = default;
    virtual void Print(const char * ptr) = 0;
};

class PrintLogDescription : public LogDescription
{
public:
    PrintLogDescription() = default;
    ~PrintLogDescription() override = default;
    void Print(const char * ptr) override;
};

inline void PrintLogDescription::Print(const char * ptr)
{
    (void)this;
    printf("%s", ptr);
}

class FileLogDescription : public LogDescription
{
public:
    FileLogDescription();
    ~FileLogDescription() override;
    void Print(const char * ptr) override;
private:
    std::string m_filename;
    int m_file {0};
};

inline FileLogDescription::FileLogDescription()
{
    char path[1024] = {};
    const char * ptr = nullptr;
    if (readlink("/proc/self/exe", path, sizeof(path) - 1) > 0 && ((ptr = strrchr(path, '/'))) && ptr)
    {
        m_filename = (ptr + 1);
    }
    else
    {
        m_filename = std::to_string(getpid());
    }
    m_filename += "_" + std::to_string(getpid()) + ".log";
    m_file      = open(m_filename.c_str(), O_WRONLY | O_CREAT, 0644);
}

inline FileLogDescription::~FileLogDescription()
{
    if (m_file >= 0)
    {
        close(m_file);
        m_file = -1;
    }
}

inline void FileLogDescription::Print(const char * ptr)
{
    if (m_file >= 0)
    {
        write(m_file, ptr, strlen(ptr));
    }
}

class LogUtilDecorator
{
public:
    enum LogDescMode
    {
        EmLogDescPrint  = 1,
        EmLogDescFile   = 2,
    };
    struct ModuleOpt
    {
        std::string m_module;
        int         m_enable{0};
    };
    explicit LogUtilDecorator(LogDescMode mode = EmLogDescPrint);
    ~LogUtilDecorator();
    static void ProcessLog(const char * module, const char * line, const char * log);
    static LogUtilDecorator* GetInstance(LogDescMode mode = EmLogDescPrint)
    {
        static LogUtilDecorator instance(mode);
        return &instance;
    }
    static const char * Log(const char * fmt,...);
    static const char * Line(const char * file, const char * function, int number);
    static void Call(const char * ptr)
    {
        GetInstance()->m_log_desc->Print(ptr);
    }
    static void InitLogUtilDecorator(const LogDescMode & mode)
    {
        auto instance = GetInstance();
        if (instance->m_log_desc != nullptr)
        {
            delete instance->m_log_desc;
            instance->m_log_desc = nullptr;
        }
        if (mode == EmLogDescPrint)
        {
            instance->m_log_desc = new PrintLogDescription();
        }
        else if (mode == EmLogDescFile)
        {
            instance->m_log_desc = new FileLogDescription();
        }
    }
    static void UpdateModuleState(const char * module, int enable)
    {
        std::unique_lock<std::mutex> lk(GetInstance()->m_log_mutex);
        auto iter = GetInstance()->m_module_opt.find(module ? module : "");
        if (iter == GetInstance()->m_module_opt.end())
        {
            GetInstance()->m_module_opt[module] = {.m_module = module, .m_enable = enable};
        }
        else
        {
            iter->second.m_enable = enable;
        }
    }
    static bool GetModuleState(const char * module)
    {
        std::unique_lock<std::mutex> lk(GetInstance()->m_log_mutex);
        auto iter = GetInstance()->m_module_opt.find(module ? module : "");
        return iter != GetInstance()->m_module_opt.end() ? iter->second.m_enable : true;
    }
private:
    LogDescription * m_log_desc;
    std::mutex m_log_mutex;
    std::map<std::string, ModuleOpt> m_module_opt;
};

inline LogUtilDecorator::LogUtilDecorator(LogDescMode mode)
{
    if (mode == EmLogDescPrint)
    {
        m_log_desc = new PrintLogDescription();
    }
    else if (mode == EmLogDescFile)
    {
        m_log_desc = new FileLogDescription();
    }
}

inline LogUtilDecorator::~LogUtilDecorator()
{
    if (m_log_desc != nullptr)
    {
        delete m_log_desc;
        m_log_desc = nullptr;
    }
}

inline const char * LogUtilDecorator::Log(const char * fmt, ...)
{
    va_list ap = {};
    va_start(ap, fmt);
    char * p = nullptr;
    const int len = vasprintf(&p, fmt, ap);
    va_end(ap);
    p[len] = '\0';
    return p;
}

static std::string NowTime()
{
    struct timeval tm = {};
    gettimeofday(&tm, nullptr);
    time_t stm = tm.tv_sec;
    struct tm time_point = {};
    localtime_r(&stm, &time_point);
    const char * ptr = LogUtilDecorator::GetInstance()->Log("%04d-%02d-%02d %02d:%02d:%02d.%06d",
        time_point.tm_year + 1900,
        time_point.tm_mon  + 1,
        time_point.tm_mday,
        time_point.tm_hour,
        time_point.tm_min,
        time_point.tm_sec,
        tm.tv_usec
        );
    std::string time_content = ptr;
    free((void*)ptr);
    return time_content;
}

inline void LogUtilDecorator::ProcessLog(const char * module, const char * line, const char * log)
{
    std::string time = NowTime();
    const char * ptr = Log("%stime:%-*smodule:%-12s%s\n\n", line, time.size() + 4, time.c_str(), module, log);
    if (GetModuleState(module))
    {
        Call(ptr);
    }
    free(const_cast<char*>(ptr));
    free(const_cast<char*>(log));
}

inline const char * LogUtilDecorator::Line(const char * file, const char * function, int number)
{
    const char * file_ptr = strrchr(file, '/');
    file_ptr = file_ptr ? ((*file_ptr) == '/' ? file_ptr + 1 : file_ptr) : file;
    const char * ptr = LogUtilDecorator::Log("file:%s:%-5d%*sfunction:%-28s", file_ptr, number, 30 - strlen(file_ptr) - 5, "", function);
    return ptr;
}


#endif //LOG_UTIL_H
